﻿namespace NoIdeaIoc;
partial class DefaultContainerBuilder
{
    const BindingFlags DefaultFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.NonPublic;
    internal static Func<LifeTime, string, object>? CreateServiceActiveFunc(Type serviceType, Type implementationType, MethodInfo resolveIocResponseMethod, MethodInfo getInstanceMethod)
    {
        if (implementationType.IsAbstract || implementationType.IsInterface) return null;
        var constructors = implementationType.GetConstructors(DefaultFlags);
        if (constructors == null || constructors.Length < 1) return null;
        var objectType = typeof(object);
        var funcName = string.Concat("DynamicNew_", Guid.NewGuid().ToString("N"));
        var mbuilder = new DynamicMethod(funcName, objectType, new Type[] { typeof(LifeTime), typeof(string) });
        var myIL = mbuilder.GetILGenerator();
        var responseInstanceLocal = myIL.DeclareLocal(objectType);
        var responseResultLocal = myIL.DeclareLocal(typeof(IocResponse));
        var tempList = constructors.ToList();
        MethodInfo? getTypeFromHandleMethod = null;
        for (var c = tempList.Count - 1; c >= 0; c--)
        {
            var constructor = tempList[c];
            var nextLabel = myIL.DefineLabel();
            var parameters = constructor.GetParameters();
            if (parameters != null && parameters.Length > 0)
            {
                var pCount = parameters.Length;
                var parameterlabels = new LocalBuilder[pCount];
                for (var i = 0; i < pCount; i++)
                {
                    var p = parameters[i];
                    string iocKeyName = null;
                    var iocKeyAttr = p.GetCustomAttributes()?.FirstOrDefault(x => typeof(FromKeyedServicesAttribute).IsAssignableFrom(x.GetType())) as FromKeyedServicesAttribute;
                    if (iocKeyAttr != null)
                    {
                        iocKeyName = iocKeyAttr.Key.ToString();
                    }
                    var parameterType = p.ParameterType;
                    var parameterLable = myIL.DeclareLocal(parameterType);
                    parameterlabels[i] = parameterLable;
                    var serviceKeyName = ResolveUnitBase.GetCacheKey(parameterType);
                    if (getTypeFromHandleMethod == null) getTypeFromHandleMethod = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle), BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(RuntimeTypeHandle) }, null);
                    myIL.Emit(OpCodes.Ldarg_0);
                    myIL.Emit(OpCodes.Ldtoken, parameterType);
                    EmitHelper.EmitCall(myIL, getTypeFromHandleMethod);
                    if (iocKeyName == null) myIL.Emit(OpCodes.Ldnull);
                    else myIL.Emit(OpCodes.Ldstr, iocKeyName);
                    EmitHelper.EmitCall(myIL, resolveIocResponseMethod);
                    myIL.Emit(OpCodes.Stloc, responseResultLocal);
                    myIL.Emit(OpCodes.Ldloc, responseResultLocal);
                    //myIL.Emit(OpCodes.Ldnull);
                    //myIL.Emit(OpCodes.Ceq);
                    myIL.Emit(OpCodes.Brfalse, nextLabel);
                    myIL.Emit(OpCodes.Ldloc, responseResultLocal);
                    EmitHelper.EmitCall(myIL, getInstanceMethod);
                    myIL.Emit(OpCodes.Stloc, responseInstanceLocal);

                    myIL.Emit(OpCodes.Ldloc, responseInstanceLocal);
                    myIL.Emit(OpCodes.Brfalse, nextLabel);
                    myIL.Emit(OpCodes.Ldloc, responseInstanceLocal);
                    if (parameterType.FullName != "System.Object")
                    {
                        if (parameterType.IsValueType) myIL.Emit(OpCodes.Unbox_Any, parameterType);
                        else myIL.Emit(OpCodes.Castclass, parameterType);
                    }
                    myIL.Emit(OpCodes.Stloc, parameterLable);
                }
                for (var i = 0; i < pCount; i++)
                {
                    var label = parameterlabels[i];
                    myIL.Emit(OpCodes.Ldloc, label);
                }
            }
            myIL.Emit(OpCodes.Newobj, constructor);
            var resultLocal = myIL.DeclareLocal(implementationType);
            myIL.Emit(OpCodes.Stloc, resultLocal);
            if (serviceType.FullName != implementationType.FullName)
            {
                myIL.Emit(OpCodes.Ldloc, resultLocal);
                if (serviceType.IsValueType) myIL.Emit(OpCodes.Unbox_Any, serviceType);
                else myIL.Emit(OpCodes.Castclass, serviceType);
            }
            else myIL.Emit(OpCodes.Ldloc, resultLocal);
            myIL.Emit(OpCodes.Ret);
            myIL.MarkLabel(nextLabel);
            //Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl
            //Microsoft.Extensions.Options.UnnamedOptionsManager
            /*
            var loadedCountLabel = myIL.DeclareLocal(typeof(int));
            myIL.Emit(OpCodes.Ldc_I4_0);
            myIL.Emit(OpCodes.Stloc, loadedCountLabel);
            myIL.Emit(OpCodes.Ldloc, loadedCountLabel);
            myIL.Emit(OpCodes.Ldc_I4_1);
            myIL.Emit(OpCodes.Add);
            myIL.Emit(OpCodes.Stloc, loadedCountLabel);
            myIL.MarkLabel(nextLabel);
            var popLabel = myIL.DefineLabel();
            myIL.Emit(OpCodes.Ldloc, loadedCountLabel);
            myIL.Emit(OpCodes.Ldc_I4_0);
            myIL.Emit(OpCodes.Cgt);
            myIL.Emit(OpCodes.Brfalse, nextLabel);
            myIL.Emit(OpCodes.Pop);
            myIL.Emit(OpCodes.Ldloc, loadedCountLabel);
            myIL.Emit(OpCodes.Ldc_I4_1);
            myIL.Emit(OpCodes.Sub);
            myIL.Emit(OpCodes.Stloc, loadedCountLabel);
            myIL.Emit(OpCodes.Br, popLabel);
            //*/
        }
        myIL.Emit(OpCodes.Ldnull);
        myIL.Emit(OpCodes.Ret);
        return (Func<LifeTime, string, object>)mbuilder.CreateDelegate(typeof(Func<LifeTime, string, object>));
    }








    internal static Func<LifeTime, string, object> CreateServiceActiveIEnumerableFunc(Type elementType)
    {
        var objectType = typeof(object);
        var mbuilder = new DynamicMethod(string.Concat("DynamicNew_", Guid.NewGuid().ToString("N")), objectType, new Type[] { typeof(LifeTime), typeof(string) });
        var myIL = mbuilder.GetILGenerator();
        var responseEnumerableMethod = LifeTime.GetResolveIocResponseEnumerableMethod();
        var tempMethod = responseEnumerableMethod.MakeGenericMethod(elementType);
        myIL.Emit(OpCodes.Ldarg_0);
        myIL.Emit(OpCodes.Ldarg_1);
        EmitHelper.EmitCall(myIL, tempMethod);
        myIL.Emit(OpCodes.Ret);
        return (Func<LifeTime, string, object>)mbuilder.CreateDelegate(typeof(Func<LifeTime, string, object>));
    }
    /// <summary>
    /// 创建Ioc容器
    /// <para>返回子容器</para>
    /// </summary>
    public ILifeTime Populate()
    {
        var serviceType = typeof(IServiceProvider);
        var iementationType = typeof(LifeTime);
        add(serviceType, iementationType, ServiceLifetime.Scoped);
        serviceType = typeof(ILifeTime);
        add(serviceType, iementationType, ServiceLifetime.Scoped);
        serviceType = typeof(IServiceScopeFactory);
        iementationType = typeof(DefaultServiceScopeFactory);
        add(serviceType, iementationType, ServiceLifetime.Singleton);
        var hashRegisted = new HashSet<string>();
        var resolveUnits = new List<ServiceContainer>(serviceDescriptors.Count);
        var resolvedResponses = new List<IocResponse>(5);
        var funcDict = new Dictionary<string, Func<LifeTime, string, object>>(5);
        MethodInfo resolveIocResponseMethod = LifeTime.GetResolveIocResponseMethod();
        MethodInfo getInstanceMethod = IocResponse.GetInstanceMethod();
        var enumerableGenericTypeDefinition = typeof(IEnumerable<>);
        var singletonHash = new HashSet<string>();
        var enumerableGenericTypeFullName = EmitHelper.GetFullName(enumerableGenericTypeDefinition.FullName);
        var hasAddenumerableGenericTypeDefinition = false;
        foreach (var descriptor in serviceDescriptors)
        {
            var serviceKey = EmitHelper.GetFullName(descriptor.ServiceType.FullName);
            var serviceUnit = resolveUnits.FirstOrDefault(x => x.EqualServiceKey(serviceKey));
            bool isNewServiceUnit;
            if (serviceUnit == null)
            {
                serviceUnit = new ServiceContainer(serviceKey);
                isNewServiceUnit = true;
            }
            else isNewServiceUnit = false;
            string cacheKey;
            IResolveUnit newResolveUnit;
            if (descriptor.IsKeyedService)
            {
                var thisKeyName = descriptor.ServiceKey.ToString();
                cacheKey = ResolveUnitBase.GetCacheKey(descriptor.ServiceType, thisKeyName);
                if (descriptor.KeyedImplementationInstance != null)
                {
                    var tempInstance = new IocResponse(cacheKey, descriptor.KeyedImplementationInstance, ServiceLifetime.Singleton);
                    resolvedResponses.Add(tempInstance);
                    continue;
                }
                if (descriptor.KeyedImplementationFactory != null)
                {
                    serviceUnit.AddService(descriptor.KeyedImplementationFactory, thisKeyName, descriptor.Lifetime);
                }
                else
                {
                    if (descriptor.KeyedImplementationType.IsGenericTypeDefinition)
                    {
                        serviceUnit.AddServiceGenericTypeDefinition(descriptor.ServiceType, descriptor.KeyedImplementationType, thisKeyName, descriptor.Lifetime);
                    }
                    else
                    {
                        var tempTypeFullName = descriptor.KeyedImplementationType.FullName;
                        if (!funcDict.TryGetValue(tempTypeFullName, out var createFunc))
                        {
                            createFunc = CreateServiceActiveFunc(descriptor.ServiceType, descriptor.KeyedImplementationType, resolveIocResponseMethod, getInstanceMethod);
                            if (createFunc == null) continue;
                            funcDict[tempTypeFullName] = createFunc;
                        }
                        serviceUnit.AddServiceFun(createFunc, thisKeyName, descriptor.Lifetime);
                    }
                }
            }
            else
            {
                cacheKey = ResolveUnitBase.GetCacheKey(descriptor.ServiceType, null);
                if (descriptor.ImplementationInstance != null)
                {
                    var tempInstance = new IocResponse(cacheKey, descriptor.ImplementationInstance, ServiceLifetime.Singleton);
                    resolvedResponses.Add(tempInstance);
                    continue;
                }
                if (descriptor.ImplementationFactory != null)
                {
                    serviceUnit.AddService(descriptor.ImplementationFactory, descriptor.Lifetime);
                }
                else
                {
                    if (descriptor.ImplementationType.IsGenericTypeDefinition)
                    {
                        serviceUnit.AddServiceGenericTypeDefinition(descriptor.ServiceType, descriptor.ImplementationType, descriptor.Lifetime);
                    }
                    else
                    {
                        var tempTypeFullName = descriptor.ImplementationType.FullName;
                        if (!funcDict.TryGetValue(tempTypeFullName, out var createFunc))
                        {
                            createFunc = CreateServiceActiveFunc(descriptor.ServiceType, descriptor.ImplementationType, resolveIocResponseMethod, getInstanceMethod);
                            if (createFunc == null) continue;
                            funcDict[tempTypeFullName] = createFunc;
                        }
                        serviceUnit.AddServiceFun(createFunc, null, descriptor.Lifetime);
                    }
                }
            }
            if (isNewServiceUnit) resolveUnits.Add(serviceUnit);
            if (enumerableGenericTypeFullName == serviceKey) hasAddenumerableGenericTypeDefinition = true;
        }
        if (!hasAddenumerableGenericTypeDefinition)
        {
            var serviceUnit = new ServiceContainer(enumerableGenericTypeFullName);
            serviceUnit.AddServiceEnumerableDefinition();
            resolveUnits.Add(serviceUnit);
        }
        LifeTime.InitRoot(resolveUnits, resolvedResponses);
        return new LifeTime();
    }
}